﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;

namespace ShortcutToolbox.Core
{
    public class Language : DynamicObject
    {
        public const string NameSpace = "http://www.beikx.com/lang/v1/";

        private Dictionary<string, object> _members = new Dictionary<string, object>();

        public ValueCollection Members
        {
            get { return new ValueCollection(_members); }
        }

        public KeyCollection Properties
        {
            get { return new KeyCollection(_members); }
        }

        public int Count { get { return _members.Count; } }

        public object this[string property]
        {
            get
            {
                string[] keys = property.Split('.');
                string key;
                object obj = this;
                int i;
                for (i = 0; obj != null && i < keys.Length - 1; i++)
                {
                    key = keys[i];
                    if (obj is DynamicObject)
                    {
                        DynamicObject dyobj = obj as DynamicObject;
                        GetMemberBinder binder = new MyGetMemberBinder(key, false);
                        if (!dyobj.TryGetMember(binder, out obj))
                            obj = null;
                    }
                    else if (obj is IDictionary)
                    {
                        IDictionary dic = obj as IDictionary;
                        if (dic.Contains(key))
                            obj = dic[key];
                        else
                            obj = null;
                    }
                    else
                        obj = null;
                }

                key = keys[i];
                if (obj != null)
                {
                    if (obj is DynamicObject)
                    {
                        DynamicObject dyobj = obj as DynamicObject;
                        GetMemberBinder binder = new MyGetMemberBinder(key, false);
                        if (dyobj.TryGetMember(binder, out obj))
                            return obj;
                    }
                    else if (obj is IDictionary)
                    {
                        IDictionary dic = obj as IDictionary;
                        if (dic.Contains(key))
                            return dic[key];
                    }
                }
                return null;
            }

            set
            {
                string[] keys = property.Split('.');
                string key;
                object obj = this;
                int i;
                for (i = 0; i < keys.Length - 1; i++)
                {
                    key = keys[i];
                    if (obj is DynamicObject)
                    {
                        DynamicObject dyobj = obj as DynamicObject;
                        GetMemberBinder binder = new MyGetMemberBinder(key, false);
                        if (!dyobj.TryGetMember(binder, out obj) || obj == null)
                        {
                            obj = new Language();
                            SetMemberBinder setbinder = new MySetMemberBinder(key, false);
                            if (!dyobj.TrySetMember(setbinder, obj))
                                obj = null;
                        }
                    }
                    else if (obj is IDictionary)
                    {
                        IDictionary dic = obj as IDictionary;
                        if (dic.Contains(key))
                            obj = dic[key];
                        else
                        {
                            obj = new Language();
                            dic.Add(key, obj);
                        }
                    }
                    else
                        obj = null;
                    if (obj == null || (!(obj is IDictionary) && !(obj is DynamicObject)))
                        throw new Exception($"Invalid property name '{property}'");
                }

                key = keys[i];

                if (obj is DynamicObject)
                {
                    DynamicObject dyobj = obj as DynamicObject;
                    SetMemberBinder binder = new MySetMemberBinder(key, false);
                    if (!dyobj.TrySetMember(binder, value))
                        throw new Exception($"Failed to set property value '{property}'");
                }
                else if (obj is IDictionary)
                {
                    IDictionary dic = obj as IDictionary;
                    if (dic.Contains(key))
                        dic[key] = value;
                    else
                        dic.Add(key, value);
                }
            }
        }

        public string GetFullName()
        {
            List<string> list = new List<string>();
            object parent;
            parent = this;
            while (parent != null)
            {
                if (parent is Language)
                {
                    Language lang = parent as Language;
                    if (lang._members.ContainsKey("name"))
                        list.Add((string)lang._members["name"]);
                    else
                        list.Add("<null>");
                    if (lang._members.ContainsKey("$parent"))
                        parent = lang._members["$parent"];
                    else
                        parent = null;
                }
                else if (parent is IDictionary)
                {
                    IDictionary dic = parent as IDictionary;
                    if (dic.Contains("name"))
                        list.Add((string)dic["name"]);
                    else
                        list.Add("<null>");
                    if (dic.Contains("$parent"))
                        parent = dic["$parent"];
                    else
                        parent = null;
                }
            }
            list.Reverse();
            StringBuilder sb = new StringBuilder();
            foreach (string s in list)
            {
                if (sb.Length > 0) sb.Append(".");
                sb.Append(s);
            }
            return sb.ToString();
        }

        #region 基类方法

        /// <summary>
        /// 实现动态对象属性成员访问的方法，得到返回指定属性的值
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            string property = binder.Name;
            if (_members.ContainsKey(property))
                result = _members[property];
            else
            {
                Logging.Error($"Language \"{GetFullName()}.{property}\" not found");
                result = null;
            }
            return true;
        }

        /// <summary>
        /// 实现动态对象属性值设置的方法。
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            string property = binder.Name;
            if (_members.ContainsKey(property))
                _members[property] = value;
            else
                _members.Add(property, value);
            return true;
        }

        public override bool TryDeleteMember(DeleteMemberBinder binder)
        {
            string property = binder.Name;
            if (_members.ContainsKey(property))
            {
                _members.Remove(property);
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// 动态对象动态方法调用时执行的实际代码
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="args"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            string property = binder.Name;
            object fun;
            if (_members.ContainsKey(property))
                fun = _members[property];
            else
                fun = null;
            if (fun != null && fun is Function)
            {
                if (fun is Function)
                {
                    result = (fun as Function).Invoke(this, args);
                    return true;
                }
                else
                {
                    throw new Exception(string.Format("Invoke non-function member 'Lang.{0}'", binder.Name));
                }
            }
            else
            {
                result = null;
                return false;
            }
        }

        public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
        {
            return base.TryInvoke(binder, args, out result);
        }

        #endregion

        public void Load(string filename)
        {
            using (FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read))
            {
                Load(stream);
            }
        }

        public void Load(Stream inStream)
        {
            using (XmlReader reader = XmlReader.Create(inStream))
            {
                Load(reader);
            }
        }

        public void Load(TextReader txtReader)
        {
            using (XmlReader reader = XmlReader.Create(txtReader))
            {
                Load(reader);
            }
        }

        public void LoadXml(string xml)
        {
            using (StringReader reader = new StringReader(xml))
            {
                Load(reader);
            }
        }

        private string ReadAttrValue(XmlReader reader, string name)
        {
            string value = reader.GetAttribute(name, NameSpace);
            if (string.IsNullOrEmpty(value))
                value = reader.GetAttribute(name);
            return value;
        }

        public void Load(XmlReader reader)
        {
            Stack<Language> stack = new Stack<Language>();
            Language lang = null, parent;
            string ns, node, pnode, name;
            while (reader.Read())
            {
                ns = reader.NamespaceURI;
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (ns != NameSpace)
                            continue;
                        node = reader.Name;
                        switch (node)
                        {
                            case "lang":
                                if (stack.Count > 0)
                                    throw new Exception("Invalid format: 'lang' should be a root node");
                                lang = this;
                                stack.Push(lang);
                                lang["node"] = node;
                                lang["name"] = name = ReadAttrValue(reader, "name");
                                lang["alias"] = ReadAttrValue(reader, "alias");
                                if (string.IsNullOrEmpty(name))
                                    throw new Exception($"Invalid format: '{node}' missing 'name' attribute");
                                break;
                            case "group":
                                if (stack.Count == 0)
                                    throw new Exception("Invalid format: 'group' cannot be a root node");
                                parent = stack.Peek();
                                pnode = Convert.ToString(parent["node"]);
                                if (pnode != "lang" && pnode != "group")
                                    throw new Exception("Invalid format: 'group' can only be the son of 'lang' and 'group'");
                                lang = new Language();
                                stack.Push(lang);
                                lang["node"] = node;
                                lang["name"] = name = ReadAttrValue(reader, "name");
                                lang["description"] = ReadAttrValue(reader, "description");
                                if (string.IsNullOrEmpty(name))
                                    throw new Exception($"Invalid format: '{node}' missing 'name' attribute");
                                parent[name] = lang;
                                lang["$parent"] = parent;
                                break;
                            case "item":
                                if (stack.Count == 0)
                                    throw new Exception("Invalid format: 'item' cannot be a root node");
                                parent = stack.Peek();
                                pnode = Convert.ToString(parent["node"]);
                                if (pnode != "lang" && pnode != "group")
                                    throw new Exception("Invalid format: 'item' can only be the son of 'lang' and 'group'");
                                name = ReadAttrValue(reader, "name");
                                if (string.IsNullOrEmpty(name))
                                    throw new Exception($"Invalid format: '{node}' missing 'name' attribute");
                                parent[name] = ReadAttrValue(reader, "value");
                                break;
                        }
                        break;
                    case XmlNodeType.EndElement:
                        if (ns != NameSpace)
                            continue;
                        node = reader.Name;
                        switch (node)
                        {
                            case "lang":
                            case "group":
                            case "item":
                                stack.Pop();
                                break;
                        }
                        break;
                    default:
                        break;
                }
            }
        }


        public string GetFormat(string property, params Formater[] pms)
        {
            object obj = this[property];
            if (!(obj is string))
                throw new Exception($"Lang.{property} is not a string");
            string s = obj as string;
            if (s != null && pms != null)
            {
                foreach (var item in pms)
                {
                    s = s.Replace(item.Key, item.Value);
                }
            }
            return s;
        }


        public static Language Create(string filename)
        {
            using (FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read))
            {
                return Create(stream);
            }
        }

        public static Language Create(Stream inStream)
        {
            using (XmlReader reader = XmlReader.Create(inStream))
            {
                return Create(reader);
            }
        }

        public static Language Create(TextReader txtReader)
        {
            using (XmlReader reader = XmlReader.Create(txtReader))
            {
                return Create(reader);
            }
        }

        public static Language CreateFromXml(string xml)
        {
            using (StringReader reader = new StringReader(xml))
            {
                return Create(reader);
            }
        }

        public static Language Create(XmlReader reader)
        {
            Language lang = new Language();
            lang.Load(reader);
            return lang;
        }



        public delegate object Function(dynamic sender, params object[] pms);

        public sealed class KeyCollection : IEnumerable<string>, IEnumerable, IReadOnlyCollection<string>
        {
            private string[] keys;

            public KeyCollection(Dictionary<string, object> dictionary)
            {
                keys = dictionary.Keys.ToArray();
            }

            public int Count { get { return keys.Length; } }

            public void CopyTo(string[] array, int index)
            {
                if (array == null)
                    throw new ArgumentNullException("array");
                if (index < 0)
                    throw new ArgumentOutOfRangeException("index");
                if ((index + keys.Length) > array.Length)
                    throw new ArgumentException("No enough space");
                for (int i = 0; i < keys.Length; i++)
                    array[index + i] = keys[i];
            }

            private MyEnumerator GetEnumerator()
            {
                return new MyEnumerator(keys);
            }

            IEnumerator<string> IEnumerable<string>.GetEnumerator()
            {
                return GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            private struct MyEnumerator : IEnumerator<string>, IDisposable, IEnumerator
            {
                private string[] keys;
                private int _index;

                public MyEnumerator(string[] keys)
                {
                    this.keys = keys;
                    _index = -1;
                }

                public string Current { get { return keys[_index]; } }

                object IEnumerator.Current { get { return Current; } }

                public void Dispose()
                {
                    keys = null;
                    _index = -1;
                }

                public bool MoveNext()
                {
                    if (_index + 1 < keys.Length)
                    {
                        _index++;
                        return true;
                    }
                    return false;
                }

                public void Reset()
                {
                    _index = -1;
                }
            }
        }

        public sealed class ValueCollection : IEnumerable<object>, IEnumerable, IReadOnlyCollection<object>
        {
            private object[] values;

            public ValueCollection(Dictionary<string, object> dictionary)
            {
                values = dictionary.Values.ToArray();
            }

            public int Count { get { return values.Length; } }

            public void CopyTo(object[] array, int index)
            {
                if (array == null)
                    throw new ArgumentNullException("array");
                if (index < 0)
                    throw new ArgumentOutOfRangeException("index");
                if ((index + values.Length) > array.Length)
                    throw new ArgumentException("No enough space");
                for (int i = 0; i < values.Length; i++)
                    array[index + i] = values[i];
            }

            private MyEnumerator GetEnumerator()
            {
                return new MyEnumerator(values);
            }

            IEnumerator<object> IEnumerable<object>.GetEnumerator()
            {
                return GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            private struct MyEnumerator : IEnumerator<object>, IDisposable, IEnumerator
            {
                private object[] values;
                private int _index;

                public MyEnumerator(object[] keys)
                {
                    this.values = keys;
                    _index = -1;
                }

                public object Current { get { return values[_index]; } }

                object IEnumerator.Current { get { return Current; } }

                public void Dispose()
                {
                    values = null;
                    _index = -1;
                }

                public bool MoveNext()
                {
                    if (_index + 1 < values.Length)
                    {
                        _index++;
                        return true;
                    }
                    return false;
                }

                public void Reset()
                {
                    _index = -1;
                }
            }
        }

        private class MyGetMemberBinder : GetMemberBinder
        {
            public MyGetMemberBinder(string name, bool ignoreCase)
                : base(name, ignoreCase)
            {

            }

            public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
            {
                return errorSuggestion;
            }
        }

        private class MySetMemberBinder : SetMemberBinder
        {
            public MySetMemberBinder(string name, bool ignoreCase)
                : base(name, ignoreCase)
            {

            }

            public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target,
                DynamicMetaObject value, DynamicMetaObject errorSuggestion)
            {
                return errorSuggestion;
            }
        }

        public class Formater
        {
            public string Key { get; set; }

            public string Value { get; set; }

            public Formater() { }

            public Formater(string key, string value)
            {
                Key = key;
                Value = value;
            }

        }
    }
}
